#include "ustc_comdef.h"
#include "ustc_custom.h"
#include "ustc_hif.h"
#include "ustc_api.h"
#include "ustc_usb_drv.h"
#include "ustc_usb_scsi.h"
#include "ustc_usb_bulk.h"
#include "ustc_usb_chap9.h"

static USB_PARA_T usb_para;
static USB_PATH_T usb_path;

USB_PATH_T *gp_usb_path;

static USTC_U16 usb_control_handler()
{
	unsigned char type,req;
	
	//bit6,bit5
	type = gp_usb_path->control_data.DeviceRequest.bRequestType & USB_REQUEST_TYPE_MASK;
	//bit3-bit0
	req = gp_usb_path->control_data.DeviceRequest.bRequest & USB_REQUEST_MASK;

	if (type == USB_STANDARD_REQUEST)
	{
		if (!gp_usb_path->configuration)
		{
			switch (req) {
				case P_FUNC_CHAP9_GETSTATUS:
					chap9_get_status();
					break;
				case P_FUNC_CHAP9_CLEARFEATURE:
					chap9_clear_feature();
					break;
				case P_FUNC_CHAP9_SETFEATURE:
					chap9_set_feature();
					break;
				case P_FUNC_CHAP9_SETADDRESS:
					chap9_set_address();
					break;
				case P_FUNC_CHAP9_GETDESCRIPTOR:
					chap9_get_descriptor();
					break;
				case P_FUNC_CHAP9_GETCONFIGURATION:
					chap9_get_configuration();
					break;
				case P_FUNC_CHAP9_SETCONFIGURATION:
					chap9_set_configuration();
					break;
				case P_FUNC_CHAP9_GETINTERFACE:
					chap9_get_interface();
					break;
				case P_FUNC_CHAP9_SETINTERFACE:
					chap9_set_interface();
					break;
				default:
					usb_stall_ep(0,PSTALL);
					break;
			}
		}	
	}	
	else if (type ==USB_CLASS_REQUEST)
	{
		switch(req)
			{
				case P_FUNC_BULK_GETMAXLUN:
					chap9_class_get_max_lun();
					break;
				default:
					usb_stall_ep(0,PSTALL);
					break;
			}
	}	
	else if (type == USB_VENDOR_REQUEST)
	{
		chap9_vendor_cmd_process();
	}	
	else
	{
		usb_stall_ep(0,PSTALL);
	}
	return 0;
}

static void usb_enum_process(void)
{
	USTC_U16 len;
	if (gp_usb_path->ep_rxdone)
	{
		if (gp_usb_path->control_state == USB_RECEIVE)
		{
			ustc_memcpy(gp_usb_path->control_data.dataBuffer + gp_usb_path->control_data.wCount,
				gp_usb_path->ep_buff_rx,gp_usb_path->ep_rx_len);
			gp_usb_path->control_data.wCount += gp_usb_path->ep_rx_len;
			if ((gp_usb_path->ep_rx_len != EP0_PACKET_SIZE) ||
				(gp_usb_path->control_data.wCount >=gp_usb_path->control_data.wLength))
			{
				gp_usb_path->setup_packet = 1;
				gp_usb_path->control_state = USB_IDLE;
			}	
		}	
		else
			gp_usb_path->control_state = USB_IDLE;

		if (gp_usb_path->setup_packet)
		{	
			usb_set_ccomp(1);
		}
		gp_usb_path->ep_rxdone = 0;
		//enable rx again
		usb_read_ep(0,NULL);
	}
	
	if (gp_usb_path->ep_txdone)
	{
		if (gp_usb_path->control_state != USB_TRANSMIT)
		{
			gp_usb_path->ep_txdone = 0;
			return;//continue;
		}	
		len = gp_usb_path->control_data.wLength - gp_usb_path->control_data.wCount;
		if (len >= EP0_PACKET_SIZE)
		{
			ustc_memcpy(gp_usb_path->ep_buff_tx,
				gp_usb_path->control_data.pData +gp_usb_path->control_data.wCount,
				EP0_PACKET_SIZE);
			usb_write_ep(0,gp_usb_path->ep_buff_tx,EP0_PACKET_SIZE);
			gp_usb_path->control_data.wCount += EP0_PACKET_SIZE;
			gp_usb_path->control_state = USB_TRANSMIT;
		}
		else
		{
			ustc_memcpy(gp_usb_path->ep_buff_tx,
				gp_usb_path->control_data.pData +gp_usb_path->control_data.wCount,
				len);
			usb_write_ep(0,gp_usb_path->ep_buff_tx,len);
			gp_usb_path->control_data.wCount += len;
			gp_usb_path->control_state = USB_IDLE;
		}
		gp_usb_path->ep_txdone = 0;
	}
}

static USTC_U16 usb_bulk_process(void)
{
	USTC_U16	fsm_change = 0;

	switch (gp_usb_path->bot_state)
	{
	case BOTFSM_IDLE:
		if (gp_usb_path->ep_rxdone)//ep1 received data from host
		{
			bulk_cbw_handler();
			gp_usb_path->ep_rxdone = 0;
			//enable rx again
			usb_read_ep(1,NULL);
		}
		if (gp_usb_path->bot_state != BOTFSM_IDLE)
			fsm_change = 1;
		else
			fsm_change= 0;
		break;
	case BOTFSM_DATAOUT:
		if (gp_usb_path->ep_rxdone)//ep1 received data from host
		{
			bulk_data_out_handler();
			gp_usb_path->ep_rxdone = 0;
			//enable rx again
			usb_read_ep(1,NULL);
			
		}
		if (gp_usb_path->bot_state != BOTFSM_DATAOUT)
			fsm_change = 1;
		else
			fsm_change= 0;
		break;
	case BOTFSM_DATAIN:
		if (gp_usb_path->in_tx ==0)
		{
			bulk_data_in_handler();
		}	
		if (gp_usb_path->bot_state != BOTFSM_DATAIN)
			fsm_change = 1;
		else
			fsm_change= 0;
		break;
	case BOTFSM_CSW:
		if (gp_usb_path->in_tx ==0)
		{
			bulk_csw_handler();
		}	
		if (gp_usb_path->bot_state != BOTFSM_CSW)
			fsm_change = 1;
		else
			fsm_change= 0;
		break;
	default:
		gp_usb_path->bot_state = BOTFSM_IDLE;
			fsm_change = 0;
		break;	
	}
	return fsm_change;
}

static void app_int_handle(void)
{
	USTC_U32	int_status=0;
	
	int_status = USTC_IO_INB(APP_INTR);

	if (int_status !=0)
	{
		//set usb state to in connecting when received any setup/in/out/ack packet
		if (int_status & (APP_INT_SETUP|APP_INT_IN|APP_INT_OUT|APP_INT_ACK))
		{
			if (gp_usb_path->usb_status!= USB_TRANSFER_DATA)
				gp_usb_path->usb_status = USB_IN_CONFIG;
		}
		//receive setup packet, read to our buffer
		if (int_status & APP_INT_SETUP)
		{
			gp_usb_path->control_data.wLength =0;
			gp_usb_path->control_data.wCount =0;

			gp_usb_path->control_data.DeviceRequest.bRequestType = USTC_IO_INB(APP_EP0_REQ_TYPE);
			gp_usb_path->control_data.DeviceRequest.bRequest = USTC_IO_INB(APP_EP0_REQEST);
			gp_usb_path->control_data.DeviceRequest.wValue = MA_INH(APP_EP0_VALUE);
			gp_usb_path->control_data.DeviceRequest.wIndex = MA_INH(APP_EP0_INDEX);
			gp_usb_path->control_data.DeviceRequest.wLength = MA_INH(APP_EP0_LENGTH);
		
			gp_usb_path->control_data.wLength = gp_usb_path->control_data.DeviceRequest.wLength;
			if (gp_usb_path->control_data.DeviceRequest.bRequestType & USB_ENDPOINT_DIRECTION_MASK)
			{
				//IN, to host
				gp_usb_path->control_state = USB_TRANSMIT;
				//gp_usb_path->usb_flag.bits.setup_packet = 1;
				gp_usb_path->setup_packet = 1;
			}	
			else//OUT,to device
			{
				//set command without data
				if (gp_usb_path->control_data.DeviceRequest.wLength ==0)
				{
					gp_usb_path->control_state = USB_IDLE;
					//gp_usb_path->usb_flag.bits.setup_packet = 1;
					gp_usb_path->setup_packet = 1;
				}
				else//set command with data
				{
					if (gp_usb_path->control_data.DeviceRequest.wLength > MAX_CONTROLDATA_SIZE)
					{
						//setup transation have more data,stall control pipe
						usb_stall_ep(0,PSTALL);
						gp_usb_path->control_state  = USB_IDLE;
					}	
					else
						gp_usb_path->control_state = USB_RECEIVE;
				}	
			}
		
		}//if setup packet

		//clear int
		USTC_IO_OUTBM(APP_INTR,int_status,0);

		//if (gp_usb_path->usb_flag.bits.setup_packet == 1)
		if (gp_usb_path->setup_packet)
		{
			usb_set_ccomp(1);
		}
	}

//	return int_status;
}

void ustc_udisk_init(USTC_U32 *cache, USTC_U8 *vendor, USTC_U8 *product)
{
	int i;
	
	ustc_memset(&usb_para, 0, sizeof(USB_PARA_T));
	ustc_memset(&usb_path, 0, sizeof(USB_PATH_T));
	gp_usb_path=&usb_path;
	gp_usb_path->p_para_usb = &usb_para;
	gp_usb_path->usb_cache = (USTC_U8*)cache;
	gp_usb_path->p_para_usb->disk=0;

	for (i=0; vendor[i]; i++)
		;
	usb_para.usb_vendor_size=i;

	for (i=0; product[i]; i++)
		;
	usb_para.usb_product_size=i;
	
	ustc_memcpy(usb_para.udisk_vendor,vendor,usb_para.usb_vendor_size);
	ustc_memcpy(usb_para.udisk_product,product,usb_para.usb_product_size);

	ustc_memcpy(usb_para.usb_vendor,vendor,usb_para.usb_vendor_size);
	ustc_memcpy(usb_para.usb_product,product,usb_para.usb_product_size);
}

void ustc_udisk_process()
{
		//setup packet
	if (gp_usb_path->setup_packet)
	{
		usb_control_handler();
		gp_usb_path->setup_packet =0;
	}

	//before configuration set; endpoint 0
	if (!gp_usb_path->configuration)
	{
		usb_enum_process();
	}
	else//bulk transfer process
	{
		usb_bulk_process();
	}
}

void ustc_udisk_isr(void)
{
	USTC_U32	int_status=0;
	USTC_U32	slen;
	USTC_U32	i;

	if (gp_usb_path== NULL)
		return ;

	//disable int to prevent re-enter
//	int_mask = interrupt_disable();

	int_status = USTC_IO_INW(USB_INT_STATUS);
	USTC_IO_OUTWM(USB_INT_STATUS,int_status,0);
	int_status&=~0x20;
	if(int_status!=0)
	{
		if (int_status & INT_NEED_RESET)
		{
			USTC_U32 tmp = gp_usb_path->usb_loop_flag;
			USB_PARA_T *tmp_para = gp_usb_path->p_para_usb;
			USTC_U8 *tmp_buf = gp_usb_path->usb_cache;
		
			ustc_memset(gp_usb_path,0,sizeof(USB_PATH_T));

			gp_usb_path->usb_loop_flag = tmp;
			gp_usb_path->usb_cache = tmp_buf;
			gp_usb_path->p_para_usb = tmp_para;

			usb_init_hw();
			//permit endpoint 0 receive
			usb_read_ep(0,NULL);
						
			
			//clear INT
//			USTC_IO_OUTWM(USB_INT_STATUS,int_status,0);
			//enable int
//			interrupt_restore(int_mask));  
			
			return ;
		}
		
		if (int_status & INT_APP_INTR)
			app_int_handle();
		
		//tx(bulk in) 
		if (int_status & INT_TX_ACK)
		{
			gp_usb_path->in_tx = 0;
			gp_usb_path->ep_txdone =1;
			
		}
		//tx nak or timeout not receive ack?
		//set tx dma again, and send the packet again when next IN transaction
		if (int_status & INT_TX_NAK)
		{
			gp_usb_path->in_tx = 0;
			usb_write_ep(gp_usb_path->configuration,gp_usb_path->ep_buff_tx,gp_usb_path->ep_tx_len);
		}

		//rx (bulk out)
		if (int_status & INT_RX_VALID)
		{
			USTC_U32 *p=(USTC_U32 *)gp_usb_path->ep_buff_rx;
			//unmask RX INT
			USTC_IO_OUTWM(USB_INT_MASK,INT_RX_DISCARD|INT_RX_VALID,~(INT_RX_DISCARD|INT_RX_VALID));
			
			gp_usb_path->ep_rx_len = USTC_IO_INWM(USB_RX_STATUS,RX_LEN) >>4;

			//GET data from FIFO
			slen = gp_usb_path->ep_rx_len/4;
			if (gp_usb_path->ep_rx_len %4)
				slen++;

#if 0
			for (i=0; i<slen; i++)
				*p++=USTC_IO_INW(USB_FIFO_RX);
#else
			for (i=0; i<slen/8; i++)
			{
				ustc_io_burst_inw(USB_FIFO_RX, p);
				p+=8;
			}

			for (i=0; i<slen%8; i++)
				*p++=ustc_io_inw(USB_FIFO_RX);
#endif
			//clear fifo
			USTC_IO_OUTWM(USB_TX_STATUS,TX_FIFO_CLEAR,TX_FIFO_CLEAR);
			USTC_IO_OUTWM(USB_TX_STATUS,TX_FIFO_CLEAR,0);

			gp_usb_path->ep_rxdone =1;
		
		}

		//rx discard,set rx dma again
		if (int_status  & INT_RX_DISCARD)
		{
			usb_read_ep(gp_usb_path->configuration,NULL);
		}

		//clear INT
//		USTC_IO_OUTWM(USB_INT_STATUS,int_status,0);
	}

	//enable int
//	interrupt_restore(int_mask);
	
	return ;
}

void ustc_udisk_mount(USTC_DISK_T *pDisk)
{
	USTC_U32 diskID=gp_usb_path->p_para_usb->disk++;
	gp_usb_path->p_para_usb->udisk_disk_start[diskID*4]=0;
	gp_usb_path->p_para_usb->udisk_disk_size[diskID*4]=pDisk->block_cnt;
	gp_usb_path->p_para_usb->udisk_disk_num[diskID] = 1;
	gp_usb_path->p_para_usb->read[diskID]=pDisk->read;
	gp_usb_path->p_para_usb->write[diskID]=pDisk->write;
	gp_usb_path->p_para_usb->clear[diskID]=0;
}

void ustc_udisk_start()
{
	ustc_memset(&g_ustc_usb_contorl, 0, sizeof(PARA_USB_GLOABL));

	gp_usb_path->p_para_usb->udisk_retry_count = 5;
	gp_usb_path->usb_status = USB_NOT_CONNECT;


	usb_init_hw();

	//permit endpoint 0 receive
	usb_read_ep(0,NULL);
}

void ustc_udisk_stop()
{

}

